Current File : /home/jeconsul/public_html/wp-content/plugins/backupwordpress/classes/class-scheduled-backup.php |
<?php
namespace HM\BackUpWordPress;
/**
* The Backup Scheduler
*
* Handles everything related to managing and running a backup schedule
*
* @uses Backup
* @uses
*/
class Scheduled_Backup {
/**
* The unique schedule id
*
* @var string
* @access private
*/
private $id = '';
/**
* The slugified version of the schedule name
*
* @var string
* @access private
*/
private $slug = '';
/**
* The raw schedule options from the database
*
* @var array
* @access private
*/
private $options = array(
'max_backups' => 3,
'excludes' => array(),
'type' => 'complete',
'reoccurrence' => 'manually',
);
/**
* Setup the schedule object
* Loads the options from the database and populates properties
*
* @param string $id
*
* @throws Exception
*/
public function __construct( $id ) {
// Verify the schedule id
if ( ! is_string( $id ) || ! trim( $id ) ) {
throw new \Exception( 'Argument 1 for ' . __METHOD__ . ' must be a non-empty string' );
}
// Store id for later
$this->id = $id;
// Load the options
$this->options = array_merge( $this->options, array_filter( (array) get_option( 'hmbkp_schedule_' . $this->get_id() ) ) );
if ( defined( 'HMBKP_SCHEDULE_START_TIME' ) && strtotime( 'HMBKP_SCHEDULE_START_TIME' ) ) {
$this->set_schedule_start_time( strtotime( 'HMBKP_SCHEDULE_START_TIME' ) );
}
// Setup the schedule if it isn't set
if ( ( ! $this->is_cron_scheduled() && $this->get_reoccurrence() !== 'manually' ) ) {
$this->schedule();
}
$this->backup_filename = implode( '-', array(
sanitize_title( str_ireplace( array(
'http://',
'https://',
'www',
), '', home_url() ) ),
$this->get_id(),
$this->get_type(),
current_time( 'Y-m-d-H-i-s' ),
) ) . '.zip';
$this->database_dump_filename = implode( '-', array(
'database',
sanitize_title( str_ireplace( array( 'http://', 'https://', 'www' ), '', home_url() ) ),
$this->get_id(),
) ) . '.sql';
$this->status = new Backup_Status( $this->get_id() );
}
/**
* Get the id for this schedule
*/
public function get_id() {
return esc_attr( $this->id );
}
/**
* Get a slugified version of name
*/
public function get_slug() {
// We cache slug in $this to save expensive calls to sanitize_title
if ( ! empty( $this->slug ) ) {
return $this->slug;
}
return $this->slug = sanitize_title( $this->get_name() );
}
/**
* Returns the given option value
*
* @param $option_name
* @return mixed The option value
*/
public function get_schedule_option( $option_name ) {
if ( isset( $this->options[ $option_name ] ) ) {
return $this->options[ $option_name ];
}
}
/**
* Get the name of this backup schedule
*
* @return string
*/
public function get_name() {
return ucwords( $this->get_type() ) . ' ' . $this->get_reoccurrence();
}
/**
* Get the type of backup
*
* @return string
*/
public function get_type() {
return $this->options['type'];
}
/**
* Set the type of backup
*
* @param string $type
*/
public function set_type( $type ) {
if ( ! isset( $this->options['type'] ) || $this->options['type'] !== $type ) {
$this->options['type'] = $type;
}
}
/**
* Get the exclude rules
*
* @return array
*/
public function get_excludes() {
return new Excludes( $this->options['excludes'] );
}
/**
* Set the exclude rules
*
* @param mixed $excludes A comma separated list or array of exclude rules
* @param bool $append Whether to replace or append to existing rules
*
* @return string
*/
public function set_excludes( $exclude_rules, $append = false ) {
// Normalize the exclude rules before we save them
$excludes = new Excludes;
$excludes = $excludes->normalize( (array) $exclude_rules );
// If these are valid excludes and they are different save them
if ( empty( $this->options['excludes'] ) || $this->options['excludes'] !== $excludes ) {
$this->options['excludes'] = $append && ! empty( $this->options['excludes'] ) ? array_merge( (array) $this->options['excludes'], (array) $excludes ) : (array) $excludes;
}
}
/**
* Get the maximum number of backups to keep
*
* @return int
*/
public function get_max_backups() {
return (int) $this->options['max_backups'];
}
/**
* Set the maximum number of backups to keep
*
* @param int $max
*
* @return WP_Error|boolean
*/
public function set_max_backups( $max ) {
$this->options['max_backups'] = $max;
}
public function get_status() {
return $this->status;
}
/**
* Back compat with old set_status mathod
*
* @deprecated 3.4 Backup->status->set_status()
*/
public function set_status( $message ) {
_deprecated_function( __FUNCTION__, '3.4', 'Backup->status->set_status()' );
$this->status->set_status( $message );
}
/**
* Get the array of services options for this schedule
*
* @param $service
* @param null $option
*
* @return array
*/
public function get_service_options( $service, $option = null ) {
if ( ! is_null( $option ) ) {
if ( isset( $this->options[ $service ][ $option ] ) ) {
return $this->options[ $service ][ $option ];
}
return array();
}
if ( isset( $this->options[ $service ] ) ) {
return $this->options[ $service ];
}
return array();
}
/**
* Set the service options for this schedule
*
* @param $service
* @param array $options
*/
public function set_service_options( $service, array $options ) {
$this->options[ $service ] = $options;
}
/**
* Get the start time for the schedule
*
* @return int timestamp || 0 for manual only schedules
*/
public function get_schedule_start_time( $gmt = true ) {
if ( 'manually' === $this->get_reoccurrence() ) {
return 0;
}
if ( ! $gmt ) {
$offset = get_option( 'gmt_offset' ) * 3600;
} else {
$offset = 0;
}
if ( ! empty( $this->options['schedule_start_time'] ) ) {
return $this->options['schedule_start_time'] + $offset;
}
$this->set_schedule_start_time( time() );
return time() + $offset;
}
/**
* Set the schedule start time.
*
* @param Int $time A valid timestamp
*/
public function set_schedule_start_time( $time ) {
$this->options['schedule_start_time'] = $time;
$this->schedule();
}
/**
* Get the schedule reoccurrence
*
*/
public function get_reoccurrence() {
return $this->options['reoccurrence'];
}
/**
* Set the schedule reoccurrence
*
* @param string $reoccurrence
*
* @return \WP_Error|null|boolean
*/
public function set_reoccurrence( $reoccurrence ) {
$hmbkp_schedules = cron_schedules();
// Check it's valid
if ( ! is_string( $reoccurrence ) || ! trim( $reoccurrence ) || ( ! in_array( $reoccurrence, array_keys( $hmbkp_schedules ) ) ) && 'manually' !== $reoccurrence ) {
return new \WP_Error( 'hmbkp_invalid_argument_error', sprintf( __( 'Argument 1 for %s must be a valid cron recurrence or "manually"', 'backupwordpress' ), __METHOD__ ) );
}
// If the recurrence is already set to the same thing then there's no need to continue
if ( isset( $this->options['reoccurrence'] ) && $this->options['reoccurrence'] === $reoccurrence && $this->is_cron_scheduled() ) {
return;
}
$this->options['reoccurrence'] = $reoccurrence;
if ( 'manually' === $reoccurrence ) {
$this->unschedule();
} else {
$this->schedule();
}
return true;
}
/**
* Get the interval between backups
*
* @return int
*/
public function get_interval() {
$hmbkp_schedules = cron_schedules();
if ( 'manually' === $this->get_reoccurrence() ) {
return 0;
}
return $hmbkp_schedules[ $this->get_reoccurrence() ]['interval'];
}
/**
* Get the next occurrence of this scheduled backup
*
*/
public function get_next_occurrence( $gmt = true ) {
$time = wp_next_scheduled( 'hmbkp_schedule_hook', array( 'id' => $this->get_id() ) );
if ( ! $time ) {
$time = 0;
}
if ( ! $gmt ) {
$time += get_option( 'gmt_offset' ) * 3600;
}
return $time;
}
public function is_cron_scheduled() {
return (bool) $this->get_next_occurrence();
}
/**
* Schedule the backup cron
*
*/
public function schedule() {
// Clear any existing hooks
$this->unschedule();
$schedule_timestamp = $this->get_schedule_start_time();
wp_schedule_event( $schedule_timestamp, $this->get_reoccurrence(), 'hmbkp_schedule_hook', array( 'id' => $this->get_id() ) );
}
/**
* Unschedule the backup cron.
*
* @return void
*/
public function unschedule() {
wp_clear_scheduled_hook( 'hmbkp_schedule_hook', array( 'id' => $this->get_id() ) );
}
/**
* Run the backup
*
*/
public function run() {
// Don't run if this schedule is already running
if ( $this->status->is_started() ) {
return;
}
// Setup our Site Backup Object
$backup = new Backup( $this->get_backup_filename(), $this->get_database_dump_filename() );
$backup->set_type( $this->get_type() );
$backup->set_excludes( $this->get_excludes() );
$backup->set_status( $this->status );
$this->do_action( 'hmbkp_backup_started', $backup );
$this->status->start( $this->get_backup_filename(), __( 'Starting backup...', 'backupwordpress' ) );
$this->status->set_status( __( 'Deleting old backups...', 'backupwordpress' ) );
// Delete old backups now in-case we fatal error during the backup process
$this->delete_old_backups();
$backup->run();
$errors = array_merge( $backup->errors, $backup->warnings );
$notices = array();
foreach ( $errors as $key => $error ) {
$key = str_replace( array( __NAMESPACE__ . '\\', '_File_Backup_Engine', '_Database_Backup_Engine' ), array( '', '', '' ), $key );
$notices[] = $key . ': ' . implode( ', ', $error );
}
Notices::get_instance()->set_notices( 'backup_errors', $notices );
$this->status->set_status( __( 'Deleting old backups...', 'backupwordpress' ) );
// Delete old backups again
$this->delete_old_backups();
$this->do_action( 'hmbkp_backup_complete', $backup );
$this->status->finish();
$this->update_average_schedule_run_time( $this->status->get_start_time(), time() );
}
public function get_backup_filename() {
if ( $this->status->is_started() ) {
$this->backup_filename = $this->status->get_backup_filename();
}
return $this->backup_filename;
}
public function get_database_dump_filename() {
return $this->database_dump_filename;
}
/**
* Hook into the actions fired in the Backup class and set the status
*
* @param $action
*/
public function do_action( $action, Backup $backup ) {
// Pass the actions to all the services
// Todo should be decoupled into the service class
foreach ( Services::get_services( $this ) as $service ) {
if ( is_wp_error( $service ) ) {
return $service;
}
$service->action( $action, $backup );
}
}
/**
* Calculate schedule run time.
*
* @param int Timestamp $end
*/
public function update_average_schedule_run_time( $start, $end ) {
if ( $end <= $start ) {
// Something went wrong, ignore.
return;
}
$diff = (int) abs( $end - $start );
if ( isset( $this->options['duration_total'] ) && isset( $this->options['backup_run_count'] ) ) {
$this->options['duration_total'] += $diff;
$this->options['backup_run_count'] ++;
} else {
$this->options['duration_total'] = $diff;
$this->options['backup_run_count'] = 1;
}
$this->save();
}
/**
* Calculates the average run time for this schedule.
*
* @return string
*/
public function get_schedule_average_duration() {
$duration = 'Unknown';
if ( ! isset( $this->options['duration_total'] ) || ! isset( $this->options['backup_run_count'] ) ) {
return $duration;
}
if ( 0 === (int) $this->options['backup_run_count'] ) {
return $duration;
}
$average_run_time = (int) $this->options['duration_total'] / (int) $this->options['backup_run_count'];
if ( $average_run_time < HOUR_IN_SECONDS ) {
$mins = round( $average_run_time / MINUTE_IN_SECONDS );
if ( $mins <= 1 ) {
$mins = 1;
}
/* translators: min=minute */
$duration = sprintf( _n( '%s min', '%s mins', $mins, 'backupwordpress' ), $mins );
} elseif ( $average_run_time < DAY_IN_SECONDS && $average_run_time >= HOUR_IN_SECONDS ) {
$hours = round( $average_run_time / HOUR_IN_SECONDS );
if ( $hours <= 1 ) {
$hours = 1;
}
$duration = sprintf( _n( '%s hour', '%s hours', $hours, 'backupwordpress' ), $hours );
}
return $duration;
}
/**
* Get the backups created by this schedule
*
* @todo look into using recursiveDirectoryIterator and recursiveRegexIterator
* @return string[] - file paths of the backups
*/
public function get_backups() {
$files = array();
if ( $handle = @opendir( Path::get_path() ) ) {
while ( false !== ( $file = readdir( $handle ) ) ) {
if ( pathinfo( $file, PATHINFO_EXTENSION ) === 'zip' && strpos( $file, $this->get_id() ) !== false && ( isset( $this->status ) && $this->get_backup_filename() !== $file ) ) {
$files[ @filemtime( trailingslashit( Path::get_path() ) . $file ) ] = trailingslashit( Path::get_path() ) . $file;
}
}
closedir( $handle );
}
krsort( $files );
return $files;
}
/**
* Delete old backups
*
* @access private
*/
public function delete_old_backups() {
if ( count( $this->get_backups() ) <= $this->get_max_backups() ) {
return;
}
array_map( array( $this, 'delete_backup' ), array_slice( $this->get_backups(), $this->get_max_backups() ) );
}
/**
* Delete a specific back up file created by this schedule
*
* @param string $filepath
*
* @return \WP_Error|boolean
*/
public function delete_backup( $filepath ) {
// Check that it's a valid filepath
if ( empty( $filepath ) || ! is_string( $filepath ) ) {
return new \WP_Error( 'hmbkp_empty_string_error', sprintf( __( 'Argument 1 for %s must be a non-empty string', 'backupwordpress' ), __METHOD__ ) );
}
// Make sure it exists
if ( ! file_exists( $filepath ) ) {
return new \WP_Error( 'hmbkp_file_error', sprintf( __( '%s doesn\'t exist', 'backupwordpress' ), $filepath ) );
}
// Make sure it was created by this schedule
if ( strpos( $filepath, $this->get_id() ) === false ) {
return new \WP_Error( 'hmbkp_backup_error', __( 'That backup wasn\'t created by this schedule', 'backupwordpress' ) );
}
unlink( $filepath );
return true;
}
/**
* Delete all back up files created by this schedule
*
*/
public function delete_backups() {
array_map( array( $this, 'delete_backup' ), $this->get_backups() );
}
/**
* Save the schedules options.
*
*/
public function save() {
// Only save them if they have changed
if ( get_option( 'hmbkp_schedule_' . $this->get_id() ) !== $this->options ) {
update_option( 'hmbkp_schedule_' . $this->get_id(), $this->options );
// Delete the schedule cache.
delete_transient( 'hmbkp_schedules' );
}
}
/**
* Cancel this schedule
*
* Cancels the cron job, removes the schedules options
* and optionally deletes all backups created by
* this schedule.
*
*/
public function cancel( $delete_backups = false ) {
// Delete the schedule options
delete_option( 'hmbkp_schedule_' . $this->get_id() );
// Delete the schedule cache.
delete_transient( 'hmbkp_schedules' );
// Clear any existing schedules
$this->unschedule();
// Delete it's backups
if ( $delete_backups ) {
$this->delete_backups();
}
}
}